﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ServiceModel.DomainServices.Client;
using System.Linq;
using MVVMFramework.Core.Model;
using System.Reflection;

namespace MVVMFramework.Ria.Model
{
    public class RiaDomainModel : IDomainModel
    {
        public DomainContext Context { get; protected set; }

        public RiaDomainModel(DomainContext context)
        {
            this.Context = context;
        }

        #region IDomainModel Members

        public void RejectChanges()
        {
            this.Context.RejectChanges();
        }

        public ISubmitOperation SaveChanges()
        {
            return new RiaSubmitOperation(this.Context.SubmitChanges());
        }

        public ISubmitOperation SaveChanges(Action<ISubmitOperation> callback, object userState)
        {
            var submitOp = this.Context.SubmitChanges();
            var riaSubmitOp = new RiaSubmitOperation(submitOp);
            riaSubmitOp.Completed += (s, e) => callback(riaSubmitOp);
            return riaSubmitOp;
        }

        public void Add<T>(T entity)
        {
            this.Context.EntityContainer.GetEntitySet(typeof(T)).Add(entity as Entity);
        }

        public void Attach<T>(T entity)
        {
            this.Context.EntityContainer.GetEntitySet(typeof(T)).Attach(entity as Entity);
        }

        public void Remove<T>(T entity)
        {
            this.Context.EntityContainer.GetEntitySet(typeof(T)).Remove(entity as Entity);
        }

        public void Detach<T>(T entity)
        {
            this.Context.EntityContainer.GetEntitySet(typeof(T)).Detach(entity as Entity);
        }

        public IDomainQuery<T> GetQuery<T>()
        {
            if (!typeof(Entity).IsAssignableFrom(typeof(T)))
                throw new ArgumentException("Only Entity types can be used with RiaDomainModel");

            BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;

            return (IDomainQuery<T>)this.GetType().GetMethod("GetDefaultQueryImpl", bindingFlags).MakeGenericMethod(typeof(T)).Invoke(this, null);
        }

        public IDomainQuery<T> GetQuery<T>(string name, params object[] args)
        {
            if (!typeof(Entity).IsAssignableFrom(typeof(T)))
                throw new ArgumentException("Only Entity types can be used with RiaDomainModel");

            return (IDomainQuery<T>)this.GetType().GetMethod("GetNamedQueryImpl").MakeGenericMethod(typeof(T)).Invoke(this, null);
        }

        public bool HasChanges
        {
            get { return Context.HasChanges; }
        }

        public bool IsSubmitting
        {
            get { return Context.IsSubmitting; }
        }

        public bool IsLoading
        {
            get { return Context.IsLoading; }
        }

        private IDomainQuery<T> GetDefaultQueryImpl<T>()
            where T : Entity
        {
            var queryMethod = this.Context
                                    .GetType()
                                    .GetMethods()
                                    .Single(m =>
                                        m.GetParameters().Length == 0
                                     && m.ReturnType.IsGenericType
                                     && m.ReturnType.GetGenericTypeDefinition() == typeof(EntityQuery<>)
                                     && m.ReturnType.GetGenericArguments()[0] == typeof(T)
                                     && m.Name.EndsWith("Query"));

            var queryFunc = new Func<EntityQuery<T>>(() =>
            {
                return queryMethod.Invoke(this.Context, null) as EntityQuery<T>;
            });

            return new RiaQuery<T>(this.Context, queryFunc);
        }

        private IDomainQuery<T> GetNamedQueryImpl<T>(string name, params object[] args)
            where T : Entity
        {
            var queryMethod = this.Context
                                    .GetType()
                                    .GetMethods()
                                    .Single(m =>
                                        m.Name.Equals(name)
                                     && m.ReturnType.GetGenericTypeDefinition() == typeof(EntityQuery<>)
                                     && m.ReturnType.GetGenericArguments()[0] == typeof(T)
                                     && m.ReturnType.IsGenericType);

            var queryFunc = new Func<EntityQuery<T>>(() =>
            {
                return queryMethod.Invoke(this.Context, args) as EntityQuery<T>;
            });

            return new RiaQuery<T>(this.Context, queryFunc);
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {

        }

        #endregion
    }
}
